//===============================================================================
// Microsoft Architecture Strategy Team
// LitwareHR - SaaS Sample Application
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
// The example companies, organizations, products, domain names,
// e-mail addresses, logos, people, places, and events depicted
// herein are fictitious.  No association with any real company,
// organization, product, domain name, email address, logo, person,
// places, or events is intended or should be inferred.
//===============================================================================


using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ServiceModel;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Shp.Runtime.Sitka.ServiceAgent.Entities;
using Shp.Runtime.Sitka.ServiceAgent.EntityMappers;
using Shp.Runtime.Sitka.ServiceAgent.Managers;
using Shp.Runtime.Sitka.ServiceAgent.Tests.Mocks;
using System.Globalization;
using Shp.Runtime.Sitka.ServiceGateway.SitkaClient;
using System.Linq;
using Shp.Runtime.Sitka.ServiceAgent.CrossTenant;
namespace Shp.Runtime.Sitka.ServiceAgent.Tests
{
    /// <summary>
    /// Summary description for RepositoryTests
    /// </summary>
    [TestClass]
    public class RepositoryFixture
    {
        private static string tenantId = string.Format("LitwareHR.Tests_{0}", Guid.NewGuid());

        #region Additional test attributes

        // Use ClassInitialize to run code before running the first test in the class
        [ClassInitialize()]
        public static void MyClassInitialize(TestContext testContext)
        {
            if (ContainerManager.ContainerExistsForTenant(tenantId))
            {
                ContainerManager.DeleteContainer(tenantId);
            }

            ContainerManager.CreateContainer(tenantId);
        }

        // Use ClassCleanup to run code after all tests in a class have run
        [ClassCleanup()]
        public static void MyClassCleanup()
        {
            ContainerManager.DeleteContainer(tenantId);
        }

        #endregion

        [TestMethod]
        [ExpectedException(typeof(InsertException))]
        public void ShouldNotCreateEntityInstanceOnRepositoryCreatedForUnexistingTenant()
        {
            using (Repository<MockEntity> repository = new Repository<MockEntity>("wrongTenant", new MockEntityMapper()))
            {
                Guid id = Guid.NewGuid();
                MockEntity obj = new MockEntity(id);

                repository.Insert(obj);
            }
        }

        [TestMethod]
        [ExpectedException(typeof(DeleteException))]
        public void ShouldNotDeleteUnexistingEntityId()
        {
            using (Repository<MockEntity> repository = new Repository<MockEntity>("wrongTenant", new MockEntityMapper()))
            {
                repository.Delete(Guid.NewGuid());
            }
        }

        [TestMethod]
        [ExpectedException(typeof(GetException))]
        public void ShouldNotGetUnexistingId()
        {
            using (Repository<MockEntity> repository = new Repository<MockEntity>("wrongTenant", new MockEntityMapper()))
            {
                repository.GetById(Guid.NewGuid());
            }
        }

        [TestMethod]
        public void ShouldCreateSimpleEntityAndDeleteIt()
        {
            using (Repository<MockCacheableEntity> repository = new Repository<MockCacheableEntity>(tenantId, new MockCacheableEntityMapper()))
            {
                Guid id = Guid.NewGuid();
                MockCacheableEntity obj = new MockCacheableEntity(id);

                repository.Insert(obj);

                MockCacheableEntity robj = repository.GetById(id);
                Assert.IsNotNull(robj);
                Assert.IsFalse(robj.IsCached);

                repository.Delete(id);

                Assert.IsNull(repository.GetById(id));
            }
        }

        public class MyEntity : ExtensibleEntity
        {
            public DateTime timeStamp;
        }

        public class MyEntityMapper : EntityMapper<MyEntity>
        {
            public override MyEntity ToType(Entity flexibleEntity)
            {
                MyEntity e = new MyEntity();
                e.Id = new Guid(flexibleEntity.Id);

                foreach (string key in flexibleEntity.Properties.Keys)
                {
                    switch (key)
                    { 
                        case "timeStamp":
                            e.timeStamp = ((DateTime)flexibleEntity.Properties[key]);
                            break;
                        default:
                            e.Fields.Add( new Field( key, flexibleEntity.Properties[key] ) );
                            break;
                    }
                }

                return e;
            }

            public override Entity FromType(MyEntity entity)
            {
                Entity fe = EntityManager.CreateEntity(typeof(MyEntity).ToString(), entity.Id.ToString());
                fe.Properties = new Dictionary<string, object>();

                fe.Properties.Add("timeStamp", entity.timeStamp);

                foreach (Field f in entity.Fields)
                {
                    fe.Properties.Add(f.Name, f.Value);
                }
                return fe;
            }
        }

        [TestMethod]
        public void ShouldCreateSimpleTypedEntityWithDateAndDeleteIt()
        {
            using (Repository<MyEntity> repository = new Repository<MyEntity>(tenantId, new MyEntityMapper(), null, new DefaultTenantManager()))
            {
                Guid id = Guid.NewGuid();
                MyEntity mye = new MyEntity();

                mye.timeStamp = DateTime.Now;
                mye.Id = id;
                mye.Fields.Add(new Field("ts2", mye.timeStamp));

                repository.Insert(mye);

                MyEntity r_mye = repository.GetById(id);

                Assert.AreEqual(mye.timeStamp.ToLocalTime(), r_mye.timeStamp.ToLocalTime());
            }
        }

        public class AnEntity : ExtensibleEntity, ICacheableEntity
        {
            public string AString { get; set; }
            public int ANumber { get; set; }
            public bool ABoolean { get; set; }

            //ICacheableEntity implementation
            public bool IsCached { set; get; }
        }

        //A generic mapper
        public class GenericMapper<T> : EntityMapper<T> where T : IEntity, new()
        {
            public override T ToType(Entity flexibleEntity)
            {
                var properties = typeof(T).GetProperties();

                var notGeneric = properties.Where(p => typeof(Entity).GetProperty(p.Name) == null)
                                           .Where(p => p.Name != "Fields" && p.Name != "IsCached" && p.Name != "Kind")
                                           .Select(p => new { Name = p.Name, Generic = false });

                var generic = flexibleEntity.Properties.Keys.Except(notGeneric.Select(p => p.Name)).Select(p => new { Name = p, Generic = true });

                var mappedProperties = notGeneric.Union(generic);

                T t = new T();
                t.Id = new Guid(flexibleEntity.Id);

                if (t.GetType().GetProperty("Kind") != null)
                {
                    t.GetType().GetProperty("Kind").SetValue(t, flexibleEntity.Kind, null);
                }

                foreach (var property in mappedProperties)
                {
                    if (property.Generic)
                    {
                        Field field = new Field(property.Name, flexibleEntity.Properties[property.Name]);
                        t.Fields.Add(field);
                    }
                    else
                    {
                        var pty = properties.Where(p => p.Name == property.Name)
                                            .SingleOrDefault();
                        object value = Convert.ChangeType(flexibleEntity.Properties[property.Name], pty.PropertyType);
                        pty.SetValue(t, value, null);

                    }
                }

                return t; ;
            }

            public override Entity FromType(T entity)
            {
                Entity f_entity = new Entity();
                f_entity.Id = entity.Id.ToString();

                if (entity.GetType().GetProperty("Kind") == null)
                {
                    f_entity.Kind = entity.GetType().ToString();
                }
                else
                {
                    f_entity.Kind = entity.GetType().GetProperty("Kind").GetValue(entity, null).ToString();
                }

                f_entity.Properties = new Dictionary<string, object>();

                var properties = entity.GetType().GetProperties();
                var notGeneric = properties.Where(p => p.Name != "Fields" && p.Name != "IsCached" && p.Name != "Id");

                foreach (var p in notGeneric)
                {
                    f_entity.Properties.Add(p.Name, p.GetValue(entity, null));
                }

                foreach (Field f in entity.Fields)
                {
                    f_entity.Properties.Add(f.Name, f.Value);
                }

                return f_entity;
            }
        }

        [TestMethod]
        public void ShouldMapFlexEntityIntoAnEntity()
        {
            Entity entity = new Entity();
            entity.Id = Guid.NewGuid().ToString();
            entity.Kind = typeof(AnEntity).ToString();
            entity.Properties = new Dictionary<string, object>();
            entity.Properties["AString"] = "Title";
            entity.Properties["ANumber"] = 1;
            entity.Properties["ABoolean"] = true;

            entity.Properties["Extensible"] = "SomeValue";

            GenericMapper<AnEntity> mapper = new GenericMapper<AnEntity>();

            AnEntity e = mapper.ToType(entity);

            Assert.AreEqual(e.AString, entity.Properties["AString"]);
            Assert.AreEqual(e.ANumber, entity.Properties["ANumber"]);
            Assert.AreEqual(e.Fields[0].Value, entity.Properties["Extensible"]);
            Assert.AreEqual(e.ABoolean, entity.Properties["ABoolean"]);
            Assert.IsFalse(e.IsCached);
        }

        [TestMethod]
        public void ShouldMapFlexEntityIntoTandBack()
        {
            Entity entity = new Entity();
            entity.Id = Guid.NewGuid().ToString();
            entity.Kind = typeof(AnEntity).ToString();
            entity.Version = 2;
            entity.Properties = new Dictionary<string, object>();
            entity.Properties["AString"] = "Title";
            entity.Properties["ANumber"] = 1;
            entity.Properties["ABoolean"] = true;

            entity.Properties["Extensible"] = "SomeValue";
            entity.Properties["Date"] = DateTime.Now;
            entity.Properties["Bool"] = true;

            GenericMapper<AnEntity> mapper = new GenericMapper<AnEntity>();

            AnEntity e = mapper.ToType(entity);

            Assert.AreEqual(e.AString, entity.Properties["AString"]);
            Assert.AreEqual(e.ANumber, entity.Properties["ANumber"]);
            Assert.AreEqual(e.Fields[0].Value, entity.Properties["Extensible"]);
            Assert.AreEqual(e.ABoolean, entity.Properties["ABoolean"]);
            Assert.IsFalse(e.IsCached);

            Entity r_b = mapper.FromType(e);

            Assert.AreEqual(entity.Id, r_b.Id);
            Assert.AreEqual(entity.Kind, r_b.Kind);
            Assert.AreEqual(entity.Properties["AString"], r_b.Properties["AString"]);
            Assert.AreEqual(entity.Properties["ANumber"], r_b.Properties["ANumber"]);
            Assert.AreEqual(entity.Properties["Extensible"], r_b.Properties["Extensible"]);
            Assert.AreEqual(entity.Properties["Date"], r_b.Properties["Date"]);
            Assert.AreEqual(entity.Properties["Bool"], r_b.Properties["Bool"]);
            Assert.AreEqual(entity.Properties["ABoolean"], r_b.Properties["ABoolean"]);
        }


        [TestMethod]
        public void ShouldINsertGenericExtensibleEntityRetrieveAndDelete()
        {
            using( Repository<GenericExtensibleEntity> r = new Repository<GenericExtensibleEntity>( tenantId, new GenericExtensibleEntityMapper(), null ) )
            {
                Guid id = Guid.NewGuid();
                GenericExtensibleEntity e = new GenericExtensibleEntity("My Entity", id);
                e.Fields.Add( new Field( "Field1", "Value1" ));
                e.SetFieldValue("Field2", "Value2");
                e.SetFieldValue("Field2", "Value_2");
                
                r.Insert(e);
                GenericExtensibleEntity r_e = r.GetById(id);

                Assert.IsNotNull(r_e);
                Assert.AreEqual(e.Id, r_e.Id);
                Assert.IsFalse(r_e.IsCached);
                Assert.AreEqual(e.Fields[0].Value, r_e.Fields[0].Value);
                Assert.AreEqual(e.Fields[1].Value, r_e.Fields[1].Value);

                r.Delete(id);
                Assert.IsNull(r.GetById(id));
            }
        }


        //[TestMethod]
        //[Ignore]
        //public void ShouldCreateBatchEntitiesAndDeleteThem()
        //{
        //    using (Repository<MockClass> repository = new Repository<MockClass>(tenantId, new MockClassMapper()))
        //    {
        //        Guid Id1 = Guid.NewGuid();
        //        Guid Id2 = Guid.NewGuid();
        //        Guid Id3 = Guid.NewGuid();
        //        MockClass[] entitiesArray = { new MockClass(Id1), new MockClass(Id2), new MockClass(Id3) };
        //        Collection<MockClass> entities = new Collection<MockClass>(entitiesArray);

        //        repository.BatchInsert(entities);

        //        Assert.IsNotNull(repository.GetById(Id1));
        //        Assert.IsNotNull(repository.GetById(Id2));
        //        Assert.IsNotNull(repository.GetById(Id3));

        //        repository.Delete(Id1);
        //        Assert.IsNull(repository.GetById(Id1));
        //        Assert.IsNotNull(repository.GetById(Id2));
        //        Assert.IsNotNull(repository.GetById(Id3));

        //        repository.Delete(Id2);
        //        repository.Delete(Id3);
        //        Assert.IsNull(repository.GetById(Id2));
        //        Assert.IsNull(repository.GetById(Id3));
        //    }
        //}

        [TestMethod]
        public void ShouldInsertEntityWithFieldAndSearchByIt()
        {
            using (Repository<MockExtendedEntity> repository = new Repository<MockExtendedEntity>(tenantId, new MockExtendedEntityMapper()))
            {
                Guid id1 = Guid.NewGuid();
                Guid id2 = Guid.NewGuid();
                string tag = "Tag 1";
                MockExtendedEntity obj1 = new MockExtendedEntity(id1, tag);
                MockExtendedEntity obj2 = new MockExtendedEntity(id2, tag);

                repository.Insert(obj1);
                repository.Insert(obj2);

                string searchQuery = string.Format(@"where e[""{0}""]==""{1}""", "Tag", "Tag 1");
                List<MockExtendedEntity> found = new List<MockExtendedEntity>(repository.Search(searchQuery));

                Assert.AreEqual(2, found.Count);
                Assert.IsTrue(found.Exists(delegate(MockExtendedEntity mock) { return id1 == mock.Id; }));
                Assert.IsTrue(found.Exists(delegate(MockExtendedEntity mock) { return id2 == mock.Id; }));

                found.ForEach(delegate(MockExtendedEntity mock)
                {
                    Assert.AreEqual("Tag 1", mock.Tag);
                });

                repository.Delete(id1);
                repository.Delete(id2);

                Assert.IsNull(repository.GetById(id1));
                Assert.IsNull(repository.GetById(id2));
            }
        }

        [TestMethod]
        public void ShouldCreateEntityWithFieldNameContainingSharpChar()
        {
            using (Repository<GenericExtensibleEntity> repository = new Repository<GenericExtensibleEntity>(tenantId, new GenericExtensibleEntityMapper()))
            {
                Guid id = Guid.NewGuid();
                GenericExtensibleEntity obj = new GenericExtensibleEntity("mockEntity", id);
                obj.Fields.Add(new Field("C#", "C# rules!"));

                repository.Insert(obj);

                GenericExtensibleEntity retrieved = repository.GetById(id);

                Assert.IsNotNull(retrieved);
                Assert.AreEqual("C# rules!", retrieved.GetFieldValue("C#"));

                repository.Delete(id);

                Assert.IsNull(repository.GetById(id));
            }
        }

        [TestMethod]
        public void ShouldGetAllEntitiesInserted()
        {
            using (Repository<MockEntity> repository = new Repository<MockEntity>(tenantId, new MockEntityMapper()))
            {
                Guid Id1 = Guid.NewGuid();
                Guid Id2 = Guid.NewGuid();
                Guid Id3 = Guid.NewGuid();
                MockEntity[] entitiesArray = { new MockEntity(Id1), new MockEntity(Id2), new MockEntity(Id3) };
                Collection<MockEntity> entities = new Collection<MockEntity>(entitiesArray);

                repository.Insert(entities[0]);
                repository.Insert(entities[1]);
                repository.Insert(entities[2]);

                List<MockEntity> entitiesList = new List<MockEntity>(repository.LoadAll());

                Assert.AreEqual(3, entitiesList.Count);
                Assert.IsTrue(entitiesList.Exists(delegate(MockEntity mock) { return Id1 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockEntity mock) { return Id2 == mock.Id; }));
                Assert.IsTrue(entitiesList.Exists(delegate(MockEntity mock) { return Id3 == mock.Id; }));
            }
        }

        [TestMethod]
        public void ShouldUpdateEntityThenDeleteIt()
        {
            using (Repository<MockExtendedEntity> repository = new Repository<MockExtendedEntity>(tenantId, new MockExtendedEntityMapper()))
            {
                Guid id = Guid.NewGuid();
                MockExtendedEntity obj = new MockExtendedEntity(id, "Original");

                repository.Insert(obj);

                obj = repository.GetById(id);
                Assert.AreEqual("Original", obj.Tag);
                
                obj.Tag = "Modified";
                repository.Update(obj);

                Assert.AreEqual("Modified", repository.GetById(id).Tag);

                repository.Delete(id);

                Assert.IsNull(repository.GetById(id));
            }
        }


        [TestMethod]
        [ExpectedException( typeof(UpdateException) )]
        public void ShouldFailToUpdateNonExistentEntity()
        {
            using (Repository<MockEntity> repository = new Repository<MockEntity>(tenantId, new MockEntityMapper(), null ))
            {
                Guid id = Guid.NewGuid();
                MockEntity obj = new MockEntity(id);
                repository.Update(obj);
            }
        }

        public const int CountTestEntities = 40;
        [TestMethod]
        public void ShouldQueryByMultipleProperties()
        {
            Random random = new Random();
            using (Repository<GenericExtensibleEntity> repository = new Repository<GenericExtensibleEntity>(tenantId, new GenericExtensibleEntityMapper()))
            {
                int[] customData = new int[CountTestEntities];
                for (int index = 0; index < CountTestEntities; index++)
                {
                    customData[index] = 0;
                }

                for (int index = 0; index < CountTestEntities; index++)
                {
                    GenericExtensibleEntity entity = new GenericExtensibleEntity("EntityTest", Guid.NewGuid());
                    bool addCustomData = true;
                    for (int countFields = 0; countFields < index; countFields++)
                    {
                        if ((random.Next(9999) < 500) && addCustomData)
                        {

                            addCustomData = false;
                            customData[countFields]++;
                            entity.Fields.Add(new Field("field" + countFields.ToString(), "valueFieldData"));
                        }
                        else
                        {
                            entity.Fields.Add(new Field("field" + countFields.ToString(), "value field" + countFields.ToString()));
                        }
                    }
                    repository.Insert(entity);
                }

                string query = "where e.Kind==\"EntityTest\" && ({0})";
                string where = string.Empty;
                for (int index = 0; index < CountTestEntities; index++)
                {

                    where += @" e[""field" + index.ToString() + @"""]==""valueFieldData""";
                    if (index < CountTestEntities - 1)
                    {
                        where += " || ";
                    }
                }
                query = String.Format(CultureInfo.InvariantCulture, query, where);

                int countEntities = 0;
                foreach (int value in customData)
                {
                    countEntities += value;
                }

                IEnumerable<GenericExtensibleEntity> entities = repository.Search(query);

                int retrievedEntities = 0;
                foreach (GenericExtensibleEntity entity in entities)
                {
                    retrievedEntities++;
                }

                Assert.IsNotNull(entities);
                Assert.AreEqual(countEntities, retrievedEntities);
            }
        }

        [TestMethod]
        public void ShouldReturnAllEntitiesQueriedFromMultipleTenants()
        {
            //Use 4 containers
            string[] tenants = { "tenant1", "tenant2", "tenant3", "tenant4" };

            //Add 100 Positions to each Container
            foreach (string t in tenants)
            {
                if (ContainerManager.ContainerExistsForTenant(t))
                {
                    ContainerManager.DeleteContainer(t);
                }

                ContainerManager.CreateContainer(t);

                //Add 100 Positions to each Container
                using (Repository<GenericExtensibleEntity> r = new Repository<GenericExtensibleEntity>(t, new GenericExtensibleEntityMapper(), null ) )
                {
                    for (int x = 0; x < 100; x++)
                    {
                        GenericExtensibleEntity e = new GenericExtensibleEntity( "MyEntity", Guid.NewGuid() );
                        e.SetFieldValue( "Field", "Value" );
                        r.Insert(e);
                    }
                }
            }

            CrossTenantSearch<GenericExtensibleEntity> xts = new CrossTenantSearch<GenericExtensibleEntity>();

            IList<GenericExtensibleEntity> results = xts.Search( tenants,
                                                                new CrossTenantSearch<GenericExtensibleEntity>.SearchHandler(Search),
                                                                @"where e[""Field""]==""Value""");


            //Cleanup
            foreach (string t in tenants)
            {
                ContainerManager.DeleteContainer(t);
            }

            Assert.AreEqual(400, results.Count);
        }

        private static List<GenericExtensibleEntity> Search(string tenantId, string query)
        {
            using (Repository<GenericExtensibleEntity> pr = new Repository<GenericExtensibleEntity>(tenantId, new GenericExtensibleEntityMapper(), null ))
            {
                return new List<GenericExtensibleEntity>(pr.Search(query));
            }
        }

        [TestMethod]
        [ExpectedException(typeof(ArgumentOutOfRangeException))]
        public void ShouldFailWhenAttemptingXTenantSearchWithMoreThan64Tenant()
        {
            string[] tenants;
            
            tenants = new string[100];

            CrossTenantSearch<GenericExtensibleEntity> xts = new CrossTenantSearch<GenericExtensibleEntity>();

            xts.Search(tenants, null, "query");
        }



        //[TestMethod]
        //[Ignore]
        //public void ShouldAsynchInsert100Entites()
        //{
        //    //string ConatinerId = "LitwareHR.TestsAsynchInserts";
        //    //ContainerManager.CreateContainer(ConatinerId);
        //    AutoResetEvent[] events = new AutoResetEvent[64];
        //    TimeSpan timeSpan;

        //    //using (Repository<MockClass> repository = new Repository<MockClass>(ConatinerId, new MockClassMapper()))
        //    using (Repository<MockClass> repository = new Repository<MockClass>(tenantId, new MockClassMapper()))
        //    {
        //        DateTime start = DateTime.Now;
        //        for (int x = 0; x < 64; x++)
        //        {
        //            events[x] = new AutoResetEvent(false);
        //            repository.AsyncInsert(new MockClass(Guid.NewGuid()), events[x]);
        //        }
        //        timeSpan = DateTime.Now - start;
        //    }

        //    SmartWaitAllHandlers(events);

        //    //using (Repository<MockClass> repository = new Repository<MockClass>(ConatinerId, new MockClassMapper()))
        //    using (Repository<MockClass> repository = new Repository<MockClass>(tenantId, new MockClassMapper()))
        //    {
        //        List<MockClass> mockList = new List<MockClass>(repository.LoadAll());

        //        Assert.AreEqual(64, mockList.Count, "Total time inserts (ms): " + timeSpan.Milliseconds.ToString());
        //    }
        //}

        //private static void SmartWaitAllHandlers(WaitHandle[] events)
        //{
        //    if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA)
        //    {
        //        //WaitAll is not suppported in STA Thread model, like testing framework
        //        //So, we wait for each handle individually
        //        foreach (WaitHandle waitHandle in events)
        //        {
        //            WaitHandle.WaitAny(new WaitHandle[] { waitHandle });
        //        }
        //    }
        //    else
        //    {
        //        WaitHandle.WaitAll(events);
        //    }
        //}
    }
}
